/**
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itranswarp.javapractice.plugin.repackaged.com.google.gson.internal;

import static com.itranswarp.javapractice.plugin.repackaged.com.google.gson.internal.$Gson$Preconditions.checkArgument;
import static com.itranswarp.javapractice.plugin.repackaged.com.google.gson.internal.$Gson$Preconditions.checkNotNull;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;

/**
 * Static methods for working with types.
 *
 * @author Bob Lee
 * @author Jesse Wilson
 */
public final class $Gson$Types {
	static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};

	private $Gson$Types() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns a new parameterized type, applying {@code typeArguments} to
	 * {@code rawType} and enclosed by {@code ownerType}.
	 *
	 * @return a {@link java.io.Serializable serializable} parameterized type.
	 */
	public static ParameterizedType newParameterizedTypeWithOwner(Type ownerType, Type rawType, Type... typeArguments) {
		return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
	}

	/**
	 * Returns an array type whose elements are all instances of
	 * {@code componentType}.
	 *
	 * @return a {@link java.io.Serializable serializable} generic array type.
	 */
	public static GenericArrayType arrayOf(Type componentType) {
		return new GenericArrayTypeImpl(componentType);
	}

	/**
	 * Returns a type that represents an unknown type that extends {@code bound}.
	 * For example, if {@code bound} is {@code CharSequence.class}, this returns
	 * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
	 * this returns {@code ?}, which is shorthand for {@code ? extends Object}.
	 */
	public static WildcardType subtypeOf(Type bound) {
		return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY);
	}

	/**
	 * Returns a type that represents an unknown supertype of {@code bound}. For
	 * example, if {@code bound} is {@code String.class}, this returns {@code ?
	 * super String}.
	 */
	public static WildcardType supertypeOf(Type bound) {
		return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
	}

	/**
	 * Returns a type that is functionally equal but not necessarily equal according
	 * to {@link Object#equals(Object) Object.equals()}. The returned type is
	 * {@link java.io.Serializable}.
	 */
	public static Type canonicalize(Type type) {
		if (type instanceof Class) {
			Class<?> c = (Class<?>) type;
			return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;

		} else if (type instanceof ParameterizedType) {
			ParameterizedType p = (ParameterizedType) type;
			return new ParameterizedTypeImpl(p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());

		} else if (type instanceof GenericArrayType) {
			GenericArrayType g = (GenericArrayType) type;
			return new GenericArrayTypeImpl(g.getGenericComponentType());

		} else if (type instanceof WildcardType) {
			WildcardType w = (WildcardType) type;
			return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());

		} else {
			// type is either serializable as-is or unsupported
			return type;
		}
	}

	public static Class<?> getRawType(Type type) {
		if (type instanceof Class<?>) {
			// type is a normal class.
			return (Class<?>) type;

		} else if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) type;

			// I'm not exactly sure why getRawType() returns Type instead of Class.
			// Neal isn't either but suspects some pathological case related
			// to nested classes exists.
			Type rawType = parameterizedType.getRawType();
			checkArgument(rawType instanceof Class);
			return (Class<?>) rawType;

		} else if (type instanceof GenericArrayType) {
			Type componentType = ((GenericArrayType) type).getGenericComponentType();
			return Array.newInstance(getRawType(componentType), 0).getClass();

		} else if (type instanceof TypeVariable) {
			// we could use the variable's bounds, but that won't work if there are
			// multiple.
			// having a raw type that's more general than necessary is okay
			return Object.class;

		} else if (type instanceof WildcardType) {
			return getRawType(((WildcardType) type).getUpperBounds()[0]);

		} else {
			String className = type == null ? "null" : type.getClass().getName();
			throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + "GenericArrayType, but <"
					+ type + "> is of type " + className);
		}
	}

	static boolean equal(Object a, Object b) {
		return a == b || (a != null && a.equals(b));
	}

	/**
	 * Returns true if {@code a} and {@code b} are equal.
	 */
	public static boolean equals(Type a, Type b) {
		if (a == b) {
			// also handles (a == null && b == null)
			return true;

		} else if (a instanceof Class) {
			// Class already specifies equals().
			return a.equals(b);

		} else if (a instanceof ParameterizedType) {
			if (!(b instanceof ParameterizedType)) {
				return false;
			}

			// TODO: save a .clone() call
			ParameterizedType pa = (ParameterizedType) a;
			ParameterizedType pb = (ParameterizedType) b;
			return equal(pa.getOwnerType(), pb.getOwnerType()) && pa.getRawType().equals(pb.getRawType())
					&& Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());

		} else if (a instanceof GenericArrayType) {
			if (!(b instanceof GenericArrayType)) {
				return false;
			}

			GenericArrayType ga = (GenericArrayType) a;
			GenericArrayType gb = (GenericArrayType) b;
			return equals(ga.getGenericComponentType(), gb.getGenericComponentType());

		} else if (a instanceof WildcardType) {
			if (!(b instanceof WildcardType)) {
				return false;
			}

			WildcardType wa = (WildcardType) a;
			WildcardType wb = (WildcardType) b;
			return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
					&& Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());

		} else if (a instanceof TypeVariable) {
			if (!(b instanceof TypeVariable)) {
				return false;
			}
			TypeVariable<?> va = (TypeVariable<?>) a;
			TypeVariable<?> vb = (TypeVariable<?>) b;
			return va.getGenericDeclaration() == vb.getGenericDeclaration() && va.getName().equals(vb.getName());

		} else {
			// This isn't a type we support. Could be a generic array type, wildcard type,
			// etc.
			return false;
		}
	}

	static int hashCodeOrZero(Object o) {
		return o != null ? o.hashCode() : 0;
	}

	public static String typeToString(Type type) {
		return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
	}

	/**
	 * Returns the generic supertype for {@code supertype}. For example, given a
	 * class {@code
	 * IntegerSet}, the result for when supertype is {@code Set.class} is
	 * {@code Set<Integer>} and the result when the supertype is
	 * {@code Collection.class} is {@code Collection<Integer>}.
	 */
	static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
		if (toResolve == rawType) {
			return context;
		}

		// we skip searching through interfaces if unknown is an interface
		if (toResolve.isInterface()) {
			Class<?>[] interfaces = rawType.getInterfaces();
			for (int i = 0, length = interfaces.length; i < length; i++) {
				if (interfaces[i] == toResolve) {
					return rawType.getGenericInterfaces()[i];
				} else if (toResolve.isAssignableFrom(interfaces[i])) {
					return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
				}
			}
		}

		// check our supertypes
		if (!rawType.isInterface()) {
			while (rawType != Object.class) {
				Class<?> rawSupertype = rawType.getSuperclass();
				if (rawSupertype == toResolve) {
					return rawType.getGenericSuperclass();
				} else if (toResolve.isAssignableFrom(rawSupertype)) {
					return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
				}
				rawType = rawSupertype;
			}
		}

		// we can't resolve this further
		return toResolve;
	}

	/**
	 * Returns the generic form of {@code supertype}. For example, if this is {@code
	 * ArrayList<String>}, this returns {@code Iterable<String>} given the input
	 * {@code
	 * Iterable.class}.
	 *
	 * @param supertype a superclass of, or interface implemented by, this.
	 */
	static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
		checkArgument(supertype.isAssignableFrom(contextRawType));
		return resolve(context, contextRawType, $Gson$Types.getGenericSupertype(context, contextRawType, supertype));
	}

	/**
	 * Returns the component type of this array type.
	 * 
	 * @throws ClassCastException if this type is not an array.
	 */
	public static Type getArrayComponentType(Type array) {
		return array instanceof GenericArrayType ? ((GenericArrayType) array).getGenericComponentType()
				: ((Class<?>) array).getComponentType();
	}

	/**
	 * Returns the element type of this collection type.
	 * 
	 * @throws IllegalArgumentException if this type is not a collection.
	 */
	public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
		Type collectionType = getSupertype(context, contextRawType, Collection.class);

		if (collectionType instanceof WildcardType) {
			collectionType = ((WildcardType) collectionType).getUpperBounds()[0];
		}
		if (collectionType instanceof ParameterizedType) {
			return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
		}
		return Object.class;
	}

	/**
	 * Returns a two element array containing this map's key and value types in
	 * positions 0 and 1 respectively.
	 */
	public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
		/*
		 * Work around a problem with the declaration of java.util.Properties. That
		 * class should extend Hashtable<String, String>, but it's declared to extend
		 * Hashtable<Object, Object>.
		 */
		if (context == Properties.class) {
			return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties!
		}

		Type mapType = getSupertype(context, contextRawType, Map.class);
		// TODO: strip wildcards?
		if (mapType instanceof ParameterizedType) {
			ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
			return mapParameterizedType.getActualTypeArguments();
		}
		return new Type[] { Object.class, Object.class };
	}

	public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
		// this implementation is made a little more complicated in an attempt to avoid
		// object-creation
		while (true) {
			if (toResolve instanceof TypeVariable) {
				TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
				toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
				if (toResolve == typeVariable) {
					return toResolve;
				}

			} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
				Class<?> original = (Class<?>) toResolve;
				Type componentType = original.getComponentType();
				Type newComponentType = resolve(context, contextRawType, componentType);
				return componentType == newComponentType ? original : arrayOf(newComponentType);

			} else if (toResolve instanceof GenericArrayType) {
				GenericArrayType original = (GenericArrayType) toResolve;
				Type componentType = original.getGenericComponentType();
				Type newComponentType = resolve(context, contextRawType, componentType);
				return componentType == newComponentType ? original : arrayOf(newComponentType);

			} else if (toResolve instanceof ParameterizedType) {
				ParameterizedType original = (ParameterizedType) toResolve;
				Type ownerType = original.getOwnerType();
				Type newOwnerType = resolve(context, contextRawType, ownerType);
				boolean changed = newOwnerType != ownerType;

				Type[] args = original.getActualTypeArguments();
				for (int t = 0, length = args.length; t < length; t++) {
					Type resolvedTypeArgument = resolve(context, contextRawType, args[t]);
					if (resolvedTypeArgument != args[t]) {
						if (!changed) {
							args = args.clone();
							changed = true;
						}
						args[t] = resolvedTypeArgument;
					}
				}

				return changed ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) : original;

			} else if (toResolve instanceof WildcardType) {
				WildcardType original = (WildcardType) toResolve;
				Type[] originalLowerBound = original.getLowerBounds();
				Type[] originalUpperBound = original.getUpperBounds();

				if (originalLowerBound.length == 1) {
					Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]);
					if (lowerBound != originalLowerBound[0]) {
						return supertypeOf(lowerBound);
					}
				} else if (originalUpperBound.length == 1) {
					Type upperBound = resolve(context, contextRawType, originalUpperBound[0]);
					if (upperBound != originalUpperBound[0]) {
						return subtypeOf(upperBound);
					}
				}
				return original;

			} else {
				return toResolve;
			}
		}
	}

	static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
		Class<?> declaredByRaw = declaringClassOf(unknown);

		// we can't reduce this further
		if (declaredByRaw == null) {
			return unknown;
		}

		Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
		if (declaredBy instanceof ParameterizedType) {
			int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
			return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
		}

		return unknown;
	}

	private static int indexOf(Object[] array, Object toFind) {
		for (int i = 0; i < array.length; i++) {
			if (toFind.equals(array[i])) {
				return i;
			}
		}
		throw new NoSuchElementException();
	}

	/**
	 * Returns the declaring class of {@code typeVariable}, or {@code null} if it
	 * was not declared by a class.
	 */
	private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
		GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
		return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
	}

	static void checkNotPrimitive(Type type) {
		checkArgument(!(type instanceof Class<?>) || !((Class<?>) type).isPrimitive());
	}

	private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
		private final Type ownerType;
		private final Type rawType;
		private final Type[] typeArguments;

		public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
			// require an owner type if the raw type needs it
			if (rawType instanceof Class<?>) {
				Class<?> rawTypeAsClass = (Class<?>) rawType;
				boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers())
						|| rawTypeAsClass.getEnclosingClass() == null;
				checkArgument(ownerType != null || isStaticOrTopLevelClass);
			}

			this.ownerType = ownerType == null ? null : canonicalize(ownerType);
			this.rawType = canonicalize(rawType);
			this.typeArguments = typeArguments.clone();
			for (int t = 0; t < this.typeArguments.length; t++) {
				checkNotNull(this.typeArguments[t]);
				checkNotPrimitive(this.typeArguments[t]);
				this.typeArguments[t] = canonicalize(this.typeArguments[t]);
			}
		}

		public Type[] getActualTypeArguments() {
			return typeArguments.clone();
		}

		public Type getRawType() {
			return rawType;
		}

		public Type getOwnerType() {
			return ownerType;
		}

		@Override
		public boolean equals(Object other) {
			return other instanceof ParameterizedType && $Gson$Types.equals(this, (ParameterizedType) other);
		}

		@Override
		public int hashCode() {
			return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
		}

		@Override
		public String toString() {
			StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1));
			stringBuilder.append(typeToString(rawType));

			if (typeArguments.length == 0) {
				return stringBuilder.toString();
			}

			stringBuilder.append("<").append(typeToString(typeArguments[0]));
			for (int i = 1; i < typeArguments.length; i++) {
				stringBuilder.append(", ").append(typeToString(typeArguments[i]));
			}
			return stringBuilder.append(">").toString();
		}

		private static final long serialVersionUID = 0;
	}

	private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {
		private final Type componentType;

		public GenericArrayTypeImpl(Type componentType) {
			this.componentType = canonicalize(componentType);
		}

		public Type getGenericComponentType() {
			return componentType;
		}

		@Override
		public boolean equals(Object o) {
			return o instanceof GenericArrayType && $Gson$Types.equals(this, (GenericArrayType) o);
		}

		@Override
		public int hashCode() {
			return componentType.hashCode();
		}

		@Override
		public String toString() {
			return typeToString(componentType) + "[]";
		}

		private static final long serialVersionUID = 0;
	}

	/**
	 * The WildcardType interface supports multiple upper bounds and multiple lower
	 * bounds. We only support what the Java 6 language needs - at most one bound.
	 * If a lower bound is set, the upper bound must be Object.class.
	 */
	private static final class WildcardTypeImpl implements WildcardType, Serializable {
		private final Type upperBound;
		private final Type lowerBound;

		public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
			checkArgument(lowerBounds.length <= 1);
			checkArgument(upperBounds.length == 1);

			if (lowerBounds.length == 1) {
				checkNotNull(lowerBounds[0]);
				checkNotPrimitive(lowerBounds[0]);
				checkArgument(upperBounds[0] == Object.class);
				this.lowerBound = canonicalize(lowerBounds[0]);
				this.upperBound = Object.class;

			} else {
				checkNotNull(upperBounds[0]);
				checkNotPrimitive(upperBounds[0]);
				this.lowerBound = null;
				this.upperBound = canonicalize(upperBounds[0]);
			}
		}

		public Type[] getUpperBounds() {
			return new Type[] { upperBound };
		}

		public Type[] getLowerBounds() {
			return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;
		}

		@Override
		public boolean equals(Object other) {
			return other instanceof WildcardType && $Gson$Types.equals(this, (WildcardType) other);
		}

		@Override
		public int hashCode() {
			// this equals Arrays.hashCode(getLowerBounds()) ^
			// Arrays.hashCode(getUpperBounds());
			return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
		}

		@Override
		public String toString() {
			if (lowerBound != null) {
				return "? super " + typeToString(lowerBound);
			} else if (upperBound == Object.class) {
				return "?";
			} else {
				return "? extends " + typeToString(upperBound);
			}
		}

		private static final long serialVersionUID = 0;
	}
}
